// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Actors.Utilities;
using System.Threading.Tasks;

namespace System.Threading.Actors
{
    internal class GatewayActorClient : ActorClient
    {

        public GatewayActorClient(Uri fabricUri, Uri actorUri, String partitionName)
            : base(fabricUri, actorUri, true, partitionName)
        {
            SetupActorClientAsync().Wait();
        }

        public new static ActorClient CreateOrConnectToActor(string fabricAddress, string applicationType, Uri actorUri, string actorTypeName, bool connectThroughGateway, int numReplicas, int numPartitions, string partitionName)
        {
            // Adding the logic directly to the gateway to handle this may be a pain.  In Windows Fabric v2, their REST API's should solve this.
            // Consider talking to an already-existing actor like ListDeployer to create a new actor?
            // In the mean time, let's pretend the actor exists and just connect to it.
            Uri fabricUri = new Uri("net.tcp://" + fabricAddress);
            return new GatewayActorClient(fabricUri, actorUri, partitionName);
        }

        public override void DeleteActor()
        {
            // @TODO: Deleting actors via a gateway is NYI
            // Adding the logic directly to the gateway to handle this may be a pain.  In Windows Fabric v2, their REST API's should solve this.
            // Consider talking to an already-existing actor like ListDeployer to delete the actor?
            // In the mean time, let's say we're done.
        }

        protected override async Task<bool> SetupActorClientAsync(bool registerFaultedCallback = true)
        {
            m_resolveState = ResolverState.Resolving;
            if (m_resolver != null)
            {
                // using custom resolver. Get endpoint Uri
                Uri uri = m_resolver(m_gatewayUri, m_actorUri);

                // create Client to resolved address
                throw new NotImplementedException("Resolver code path in SetupActorClient NYI.");  // Can we implement this without TcpClient?
//                TcpClient actorClient = new TcpClient(uri.Host, uri.Port);
//                m_actorClient = new ActorRawTcpClient(actorClient, this, m_useGateway ? m_actorUri.ToString() : uri.LocalPath, partitionName);
            }
            else
            {
                m_actorTcpClient = await AzureClientSetupAsync(m_gatewayUri, m_actorUri).ConfigureAwait(continueOnCapturedContext:false);
            }

            // If requested, register for a callback when the channel faults
            if (registerFaultedCallback)
            {
                // Receive (proactive) notification if the channel faults
                m_actorTcpClient.Faulted += new EventHandler(GatewayActorClient_Faulted);
            }

            m_resolveState = ResolverState.Resolved;
            return true;
        }

        // Returns an IActor proxy for an actor/service running on Azure
        private async Task<ActorRawTcpClient> AzureClientSetupAsync(Uri gatewayAddress, Uri serviceName)
        {
            Exception resultException = null;
            for (int i = 0; i < CONNECT_RETRIES; i++)
            {
                try
                {
                    ITcpClient tcpClient = LightupITcpClientFactory.CreateTcpClientFunc(gatewayAddress.Host, gatewayAddress.Port);
                    var candidate = await ActorRawTcpClient.CreateAsync(tcpClient, this, serviceName.ToString(), m_partitionName).ConfigureAwait(continueOnCapturedContext:false);
                    if (await TestConnectivityAsync(candidate).ConfigureAwait(continueOnCapturedContext:false))
                        return candidate;
                    candidate.Dispose();
                }
                catch (EndOfStreamException eos)
                {
                    resultException = eos;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception initializing azure client({0},{1}): {2}.  Retrying",
                        gatewayAddress.ToString(), serviceName.ToString(), e.Message);
                    resultException = e;
                }

                await Task.Delay(CONNECT_RETRY_WAIT_TIME_MS).ConfigureAwait(continueOnCapturedContext:false); // Wait a bit before retrying
            }

            // If we don't succeed after a number of retries, then throw the most recent failure
            throw resultException;
        }

        // Fault handler for underlying channel.
        void GatewayActorClient_Faulted(object sender, EventArgs e)
        {
            lock (m_operationLockObject) // lock out operations while we're recovering from fault
            {
                if (m_actorTcpClient != null && m_actorTcpClient.IsFaulted)
                {
                    m_resolveState = ResolverState.Resolving;

                    var message = String.Format("GatewayActorClient({0}) detected faulted channel, entering disconnect recovery logic.", m_actorUri);
                    Trace.WriteLine(message, "Error");
                    Console.WriteLine(message);
                    var recoverTask = RecoverFromDisconnectAsync();
                    // Our previous implementation kept calling RecoverFromDisconnectAsync then blocking on its Result.
                    // We don't want to synchronously block like that, and we need to accomodate cases where the underlying
                    // stream got closed.  This may not be good enough yet, but is it close.
                    while (!recoverTask.Result && !m_disposed && !m_actorTcpClient.IsShuttingDown)
                        Task.Delay(1000).Wait();
                }
                else
                {
                    Trace.WriteLine(String.Format("Fault handler({0}): not faulted, no action taken", m_actorUri), "Info");
                }
            }
        }
    }
}
